package cn.tyz.kotlin.ui

import android.os.Bundle
import android.util.Log
import cn.tyz.kotlin.R
import cn.tyz.kotlin.utils.LogUtil
import java.util.Arrays.asList
import java.util.concurrent.locks.Lock

/**
 * @author : tyz
 * @ClassName: BasicActivity
 * @Description: 基本语法
 * @time 2018/5/19 22:36
 *
 * Java 有 static 关键字，在 Kotlin 中没有这个关键字，你需要使用@JvmStatic替代这个关键字。
 * 3.3.2 包级别函数
与 Java 不同，Kotlin 允许函数独立存在，而不必依赖于某个类，这类函数我们称之为包级别函数(Package-Level Functions)。
为了兼容 Java，Kotlin 默认会将所有的包级别函数放在一个自动生成的叫ExampleKt的类中， 在 Java 中想要调用包级别函数时，需要通过这个类来调用。
当然，也是可以自定义的，你只需要通过注解@file:JvmName("Example")即可将当前文件中的所有包级别函数放到一个自动生成的名为 Example 的类中。


 */
class BasicActivity: BaseActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.act_common)
        var hello = Hello()
        hello.people("Android"){ hello.sayMsg("World") }
        hello.people("Android",  { hello.sayMsg("World") } )
        hello.sayMsg("sayMsg")

        callFun("str")  // 这样是可以的，因为后面的参数都具有默认值
        callFun("str",true,2,2.0f,2)   // 这样阅读性很差，因为你除了看函数的定义外，你不知道这些参数的含义
        callFun("str",isTrue = true,numA = 3, numB = 3.0f, numC = 3)
        showFun()

        val test = if (5 > 3) {
            Log.i("BasicActivity","闭包 yes")
        } else {
            Log.i("BasicActivity","闭包 ")
        }
        test


        { x: Int, y: Int ->
            Log.i("BasicActivity","自执行闭包就是在定义闭包的同时直接执行闭包，一般用于初始化上下文环境。")
            println("${x + y}")
        }(1, 3)


        Log.i("BasicActivity","!! 操作符 不判断空指针")
        var  str = "adb"
        Log.i("BasicActivity","!! 操作符 不判断空指针"+ str!!.length)


        showInfix()
        showLambda()
        showGaoJieFun()
        showAnonymousFunctions()
        showLambdReturn()
        showAnonymousFunctionsReturn()
        showClosures()
        showFunctionReceiver()
    }
    /**2.3 函数
    2.3.1 函数的声明
    函数使用关键字fun声明，如下代码创建了一个名为say()的函数，它接受一个String类型的参数，并返回一个String类型的值
    同时，在 Kotlin 中，如果像这种简单的函数，可以简写为fun say(str: String): String = str

     */
    fun say(str: String): String {
        return str
    }

    /** 如果是返回Int类型，那么你甚至连返回类型都可以不写    */
    fun getIntValue(value: Int) = value

    /** 无返回值的函数   Unit   */
    fun voidFun(value: Int) : Unit{
        println("我是返回值为Unit的函数，Unit可省略")
    }

    /**   2.3.2 函数的默认参数
    你也可以使用默认参数来实现重载类似的功能*/
    fun sayWithDefault(str: String = "hello"): String = str

    /**
    这时候你可以调用say()，来得到默认的字符串 "hello"，也可以自己传入参数say("world")来得到传入参数值。
    有时参数非常多的时候，也可以使用多行参数的写法，它们是相同的*/
    fun say(firstName: String = "姓",
            lastName: String = "名"){
    }

    /** 2.3.3 变参函数
    同 Java 的变长参数一样，Kotlin 也支持变长参数
    在Java中，我们这么表示一个变长函数
    public boolean hasEmpty(String... strArray){
    for (String str : strArray){
    if ("".equals(str) || str == null)
    return true;
    }
    return false;
    }
     */

    //在Kotlin中，使用关键字vararg来表示
    fun hasEmpty(vararg strArray: String?): Boolean{
        for (str in strArray){
            if ("".equals(str) || str == null)
                return true
        }
        return false
    }



    /** 2.3.5 将函数作为参数
    Kotlin 中，可以将一个函数作为参数传递给另一个函数*/
    class Hello {

        fun sayMsg(msg: String) {
            Log.i("BasicActivity","将函数作为参数  Hello $msg")
        }

        /**
         * 当调用的函数有形参时，
         * 需要在调用的函数声明，并使用声明的形参；
         * 函数参数中的形参无法使用
         */
        fun people(arg0: String, say: (arg1: String) -> Unit) {
            Log.i("BasicActivity","将函数作为参数  $arg0  "+say(arg0))
            say(arg0)
            // hello(arg1) 这样调用将报错
        }

    }

    /**3.3、命名参数
    即在使用函数时显示使用参数名 = 参数值这种方式传递参数*/
    fun callFun(str : String,
                isTrue : Boolean = false,
                numA : Int = 2,
                numB: Float = 2.0f,
                numC : Int = 2){
        Log.i("BasicActivity","命名参数  str=$str, isTrue=$isTrue,numA=$numA,numB=$numB,numC=$numC")
    }

   /** 5.2.2 运算符重载*/
    fun showFun() {
       LogUtil.i("BasicActivity","运算符重载")
        for (i in 1..100 step 20) {
            print("$i ")
        }
       for (i in 1.rangeTo(100) step 20) {
           print("$i ")
       }
    }

   /** 5.2.3 中缀表达式
    运算符的数量毕竟是有限的，有时并不一定有合适的。
   中缀标记的方法使用关键字’infix’，它的使用有一些限制条件：
   （1）必须是成员方法或者是扩展方法
   （2）有一个参数
   （3）使用关键字’infix’
    */
   infix fun IntProgression.step(step: Int): IntProgression {
       return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
   }

    infix fun Int.add(x:Int):Int{
        return this+x
    }

    fun showInfix(){
        LogUtil.i("BasicActivity","=================== 中缀表达式 ================")
        val value=2 add 3//中缀表示法调用add函数
        val value2 = 2.and(3)//中缀表示法调用add函数
        LogUtil.i("BasicActivity","2 add 3  = $value " )
        LogUtil.i("BasicActivity","2.and(3) = $value2" )
    }

    /** 5.3.1 Lambda 表达式
     * Lambda 表达式还有非常多的语法糖，比如
    当参数只有一个的时候，声明中可以不用显示声明参数，在使用参数时可以用 it 来替代那个唯一的参数。
    当有多个用不到的参数时，可以用下划线来替代参数名(1.1以后的特性)，但是如果已经用下划线来省略参数时，是不能使用 it 来替代当前参数的。
    Lambda 最后一条语句的执行结果表示这个 Lambda 的返回值。
    需要注意的是：闭包是不能有变长参数的

    Lambda表达式语法：
    1. lambda 表达式总是被大括号括着；
    2. 其参数（如果有的话）在 -> 之前声明（参数类型可以省略）；
    3. 函数体（如果存在的话）在 -> 后面
    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            viewClicked(view);
        }
    });
    view.setOnClickListener { v -> viewClicked(v) }
    完整写法
    val imageClick: (v: View) -> Unit = {v -> viewClicked(v) }
    view.setOnClickListener(imageClick) // Lambda表达式也可以传递给一个高阶函数当做参数

    完整写法
    val sum = { x: Int, y: Int -> x + y }  //  参数   返回类型   函数体
    lambda 表达式总是被大括号括着， 完整语法形式的参数声明放在括号内，并有可选的类型标注，
    函数体跟在一个 -> 符号之后。如果推断出的该 lambda 的返回类型不是 Unit，那么该 lambda 主体中的最后一个（或可能是单个）表达式会视为返回值。


    在 Kotlin 中有一个约定，如果函数的最后一个参数是一个函数，并且你传递一个 lambda 表达式作为相应的参数，你可以在圆括号之外指定它
    因此可以实现如下
    view.setOnClickListener({v -> viewClicked(v) })
    =>
    view.setOnClickListener() {v -> viewClicked(v) }

    在 Kotlin中还有另外一个约定，如果一个函数的参数只有一个，并且参数也是一个函数，那么可以省略圆括号
    view.setOnClickListener() {v -> viewClicked(v) }
    =>
    view.setOnClickListener{v -> viewClicked(v) }

    若函数参数对应的函数只有一个参数，在使用时，可以省略参数定义，直接使用“it”代替参数：

    具有块代码体的函数，必须显式指定返回值类型，除非返回值类型是Unit
     */

    fun showLambda(){

        Log.i("BasicActivity","Lambda 表达式俗称匿名函数")
        val printMsg = { msg: String ->
            Log.i("BasicActivity","Lambda 表达式 $msg")
        }
        printMsg.invoke("hello")
        printMsg("hello") // 如果还想省略，你还可以在调用时直接省略invoke，像函数一样使用。

        val sum1 = {x: Int, j: Int -> x + j}
        Log.i("BasicActivity","Lambda 表达式 第一种 val sum1 = {x: Int, j: Int -> x + j}   sum1(1,2)=" +sum1(1,2))
        val sum2: (x: Int, j: Int) -> Int = {a, b -> a + b }
//        (x: Int, j: Int) -> Int这种表述方式就是表达函数的类型，它表示的是一个需要传入两个Int类型参数，并返回Int类型的函数。
        Log.i("BasicActivity","Lambda 表达式 第二种 val sum2: (x: Int, j: Int) -> Int = {a, b -> a + b }    sum2(1,2)=" +sum2(1,2))

        var ints = asList(1, 2, 3, 4, 6, 9)
        val doubledList = ints.map { it -> it * 2 }  // it -> it * 2   等价 it * 2
        Log.i("BasicActivity","若函数参数对应的函数只有一个参数，在使用时，可以省略参数定义，直接使用“it”代替参数")
        Log.i("BasicActivity","doubledList=" +doubledList.toString())


        val doubledList2 = ints.filter { it % 3 ==0 }
                .sortedBy { it }
                .map { it * 2 }
        Log.i("BasicActivity","doubledList2=" +doubledList2.toString()) // [6, 12, 18]

        var strings = asList("A","Ceeeeee","E","R","B","H")
        var newStr = strings.filter { it.length > 5 }
                .sortedBy { it }
                .map { it.toUpperCase() }
        Log.i("BasicActivity","newStr =" +newStr) // [CEEEEEE]



        fun String.upper(body:(String)->String):String{
            return body(this)
        }
        fun transform(){
            "HelloKotlin".upper { it.toUpperCase() }    //lambda表达式是调用的唯一参数，则调用的圆括号可以省略
            "HelloKotlin".upper(fun(str:String):String{  //将匿名函数作为一个函数参数传入
                return str.toUpperCase()
            })
        }
        transform()
    }

    /** 5.3.2 高阶函数
      该函数的参数“body”是一个函数类型：“() -> T”，表示为一个函数，没有入参，返回一个“T”的值。
    fun 高阶函数名(参数函数名：参数函数类型)：高阶函数返回类型{
        高阶函数体
        ...
    }
     */
    fun showGaoJieFun(){
        val log = { str: String, printLog: (String) -> Unit ->
            Log.i("BasicActivity","===============高阶函数=============== "+str)
        }
        val secondP = {str: String -> }
        log("测试高阶函数", secondP)

        val sum1 = {x: Int, j: Int -> x + j}
        val sum2: (x: Int, j: Int) -> Int = {a, b -> a + b }
        LogUtil.i("BasicActivity","sum1 is $sum1  sum1(1,2)=" +sum1(1,2))
        LogUtil.i("BasicActivity","sum2 is $sum2  sum2(1,2)=" +sum2(1,2))

        val max = {x: Int, y: Int -> x > y}
        val biggerNum = highOrderFunc(60, 80, max)
        LogUtil.i("BasicActivity","biggerNum is $biggerNum")
    }

    fun highOrderFunc(arg1: Int, arg2: Int, paramFunc: (a: Int, b: Int) -> Boolean): Int {
        return if (paramFunc(arg1, arg2)) {
            arg1
        } else {
            arg2
        }
    }

    fun <T> lock(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        }
        finally {
            lock.unlock()
        }
    }

  /**
    匿名函数（Anonymous Functions）
    前面的Lambda表示定义时，可以明确定义返回值类型；在大部分情况下，没有必要明确定义的，因为返回值类型基本都可以自动推断出。
    需要明确定义返回值类型，也可以使用匿名函数（anonymous function）代替。

    匿名函数 与 Lambda表示式区别：
    Ø  匿名函数作为参数，一般定义在“()”中；而Lambda表达式可以定义到调用函数“()”外。
    Ø  另外区别在“非局部返回（non-local returns）”行为上：非标签注解的return（返回对应的最内层的函数（即fun）），
       在匿名函数中，退出该匿名函数；而在Lambda表达中，退出包含该表达式的函数。
   */

    fun showAnonymousFunctions(){
          var ints = asList(1, 2, 3, 4, 6, 9)
          val result = ints.filter(fun(item) = item > 3)
      Log.i("BasicActivity","匿名函数  result = " +result.toString()) //


      var intList = asList(1, 2, 3, 4)
      Log.i("BasicActivity","匿名函数  intList = " +intList.toString())
      var reusltList = intList.map {
          it * 2
          return  // 在Lambda中使用return 退出包含该表达式的函数
      }
      Log.i("BasicActivity","匿名函数  intList = " +intList.toString()) // 这里不执行
  }

    //在Lambda中使用return
    fun showLambdReturn() {
        var intList = asList(1, 2, 3, 4)
        Log.i("BasicActivity","在Lambda中使用return  intList = " +intList.toString())
        var resultList = intList.map {
            return@map it * 2 // 返回到map, 后面的代码可以继续执行
        }
        Log.i("BasicActivity","在Lambda中使用return  resultList = " +resultList.toString())
        var resultList2 = intList.map {
            it * 2
            return  // 返回到 showLambdReturn()，后面的代码不执行
        }
        Log.i("BasicActivity","在Lambda中使用return  resultList2 = " +resultList2.toString())
    }

    //在匿名函数中使用return
    fun showAnonymousFunctionsReturn() {
        var intList = asList(1, 2, 3, 4)
        Log.i("BasicActivity","在匿名函数中使用return  intList = " +intList.toString())
        var resultList = intList.map(fun(item: Int): Int {
            return item * 2  // 在匿名函数中，退出该匿名函数 相当于 return@map it * 2
        })
        Log.i("BasicActivity","在匿名函数中使用return  resultList = " +resultList.toString())
    }

   /** 闭包（Closures）
   Lambda表达式及匿名函数（以及局部函数，对象表达式）可以访问包含它的外部范围定义的变量（Java中只能是常量，在Kotlin中可以是变量）：
    * */
    fun showClosures(){
       var sum = 0
       var ints = asList(1, 2, 3, 4)
       ints.filter { it > 0 }.forEach {
           sum += it
       }
       Log.i("BasicActivity","闭包 "+ sum.toString())
   }

  /**    函数文本接收器（Function Literals with Receiver）
    Kotlin提供一种特殊的接收者对象（ receiver object），可以访问函数文本。在函数文本的内部，可以访问接收者的成员；
  类似于扩展函数，在函数体中访问接收者的成员。*/
    fun showFunctionReceiver(){
      val sum  : Int.(other: Int) -> Int = { this + it }
      val sum2 = fun Int.(other: Int): Int = this + other  // 匿名函数方式

      Log.i("BasicActivity","函数文本接收器 1.sum(2)="+ 1.sum(2))
      Log.i("BasicActivity","函数文本接收器 1.sum2(2)="+ 1.sum2(2))
  }
}
